/**
 * @file heap_senya.cpp
 *
 * @brief heap implementation by senya
 *
 */

#include "heap_senya.h"

heap_senya::heap_senya( size_t total, alloc_flags flag) : heap876(total, flag, "senya") {
	if(sync) InitializeCriticalSection(&glob_cs);

	LOG << "constructing heap_senya" << std::endl;

	last_error = ok;

	total_limit = total = round_bytes(total);
	all_mem = new byte[total + 2*oh_occ]; //throw bad alloc exception if no memory
	LOG << total + 2*oh_occ << " bytes allocated by 'new' succesfully" << std::endl;

	memory_init();

	LOG << "base block created" << std::endl;
	LOG << "  " << block_info(begin_block) << std::endl;
	LOG << "end block:" << std::endl;
	LOG << "  " << block_info(end_block) << std::endl;
	LOG << "END constructing heap_senya" << std::endl << std::endl;
}

void*	heap_senya::alloc( size_t sz, unsigned int tag) {
	if(sync) EnterCriticalSection(&glob_cs);
	assert(tag != 'free' && "'free' is reserved");
	LOG << "allocating " << sz << " bytes, tag = '" << tagstr(tag) << '\'' << std::endl;


	sz = round_bytes(oh_occ + sz); //real block size

	_block *pbl = free_blocks.get(sz);

	if( 0 == pbl ) {
		LOG << "allocation failed: no memory" << std::endl;
		last_error = no_memory;
		return NULL;
	}

	if(flag & alloc_debug) {
		if( ! check_block_alloc(pbl) ) {
			last_error = debug_dirty_block;
			return NULL;
		}
	}

	assert(pbl->free());
	assert(!pbl->next_free || (pbl->i == pbl->next_free->i && pbl->j == pbl->next_free->j));
	assert(pbl == pbl->next()->prev());

	assert(!pbl->is_prev_free() && "free block before free block ??");
	assert(sizeof(_block) + 1 < cell_size && "min free block size < cell_size");
	if(sz < pbl->get_size()) {
		// Split block
		size_t tmp = pbl->get_size() - sz;

		pbl->size = sz;
		pbl->next()->size = tmp;
		insert_block(pbl->next());
	} else {
		pbl->next()->is_prev_free(0);
	}

	assert(!pbl->next()->is_prev_free() && "allocated block must not be free =)");

	if(flag & alloc_tagged) {
		tags[pbl] = tag;
		tag_tree[tag].blocks.insert(pbl);
		tag_tree[tag].sum += pbl->size;
	}

	LOG << "block successfully allocated:" << std::endl;
	LOG << "  " << block_info(pbl) << std::endl;
	LOG << std::endl;

	if(flag & alloc_debug) {
		fill_block_alloc(pbl);
		alloc_blocks.insert(pbl);
	}

	occupancy += pbl->get_size();

	if(sync) LeaveCriticalSection(&glob_cs);
	return (void *) pbl->ptr();
}

void	heap_senya::free( void* ptr ) {
	if(sync) EnterCriticalSection(&glob_cs);
	LOG << "free " << ptr << std::endl;

	_block *pbl = (_block *)((size_t *)ptr - 1);

	if(flag & alloc_debug) {
		if(! alloc_blocks.erase(pbl)) {
			last_error = bad_free;
			return;
		}
	}

	assert( pbl >= begin_block );
	assert( pbl < end_block );
	assert( ! pbl->free() );

	if(flag & alloc_tagged) {
		int tag = tags[pbl];
		tags.erase(pbl);
		tag_tree[tag].blocks.erase(pbl);
		tag_tree[tag].sum -= pbl->get_size();
	}

	occupancy -= pbl->get_size();

	_block *next = pbl->next();

	//connecting blocks
	if(pbl->is_prev_free()) {
		_block *prev = pbl->prev();
		free_blocks.erase(prev);

		if(next->free()) { // [prev+pbl+next]
			free_blocks.erase(next);
			prev->size += pbl->get_size() + next->get_size();
		} else { // [prev+pbl]
			prev->size += pbl->get_size();
		}

		insert_block(prev);
	} else {
		if(next->free()) { // [pbl+next]
			free_blocks.erase(next);
			pbl->size += next->get_size();
		}

		insert_block(pbl);
	}

	LOG << std::endl;
	if(sync) LeaveCriticalSection(&glob_cs);
}

void	heap_senya::free_all( unsigned int tag ) {
	if(tag != 0) /// I dont like this feature
		exit(-1);

	last_error = ok;

	free_blocks.clear();

	if(flag & alloc_tagged) {
		tags.clear();
	}

	if(flag & alloc_debug) {
		alloc_blocks.clear();
	}

	memory_init();
}

void	heap_senya::memory_init() {
	occupancy = 0;

	begin_block = (_block *) all_mem;
	begin_block->size = total_limit;

	end_block = (_block *) (all_mem + total_limit);
	end_block->size = oh_occ + 1;
	end_block->next()->size = oh_occ;

	insert_block(begin_block);
}

size_t heap_senya::get_occupancy() const {
	return occupancy; //
}
